`timescale 1ns / 1ps

module fc_top(
    input  wire         clk,
    input  wire         rst_n,
    input  wire         start,

    output wire [31:0]  fc1_output_data_monitor,
    output wire         fc1_output_wren_monitor,

    output wire [47:0]  fc2_output_data_monitor,
    output wire         fc2_output_wren_monitor,

    output wire signed [63:0] fc3_output_data_monitor,
    output wire         fc3_output_wren_monitor,

    output wire         done_o
    );

    // Internal signals for fc_1
    wire [1:0]  fc1_input_data_addr;
    wire [1023:0] fc1_input_data;
    wire [9:0]   fc1_weight_addr;
    wire [1023:0] fc1_weight;
    wire [6:0]   fc1_bias_addr;
    wire [7:0]   fc1_bias;
    wire [1023:0] fc1_mul_data1;
    wire [1023:0] fc1_mul_data2;
    wire [2047:0] fc1_mul_result;
    wire [31:0]  fc1_output_data;
    wire         fc1_output_wren;
    wire         done_fc1;

    // Internal signals for fc_2
    wire [1:0]  fc2_input_data_addr;
    wire [1023:0] fc2_input_data;
    wire [6:0]   fc2_weight_addr;
    wire [255:0] fc2_weight;
    wire [4:0]   fc2_bias_addr;
    wire [7:0]   fc2_bias;
    wire [1023:0] fc2_mul_data1;
    wire [255:0] fc2_mul_data2;
    wire [1279:0] fc2_mul_result;
    wire [47:0]  fc2_output_data;
    wire         fc2_output_wren;
    wire         done_fc2;

    // Internal signals for fc_3
    wire [1535:0] fc3_input_data;
    wire         fc3_weight_addr;
    wire [255:0] fc3_weight;
    wire         fc3_bias_addr;
    wire [7:0]   fc3_bias;
    wire [1535:0] fc3_mul_data1;
    wire [255:0] fc3_mul_data2;
    wire [1791:0] fc3_mul_result;
    wire signed [63:0]  fc3_output_data;
    wire         fc3_output_wren;
    wire         done_fc3;

    

    // Instantiate fc_1
    fc_input_ram u_fc1_input_ram(
        .addra(fc1_input_data_addr),
        .clka(clk),
        .douta(fc1_input_data),
        .wea(1'b0)
    );

    fc1_weight_ram u_fc1_weight(
        .addra(fc1_weight_addr),
        .clka(clk),
        .douta(fc1_weight),
        .wea(1'b0)
    );

    fc1_bias_ram u_fc1_bias(
        .addra(fc1_bias_addr),
        .clka(clk),
        .douta(fc1_bias),
        .wea(1'b0)
    );

    mult_array1 u_fc1_mult_array(
        .clk(clk),
        .rst_n(rst_n),
        .data1(fc1_mul_data1),
        .data2(fc1_mul_data2),
        .result(fc1_mul_result)
    );

    fc_1 u_fc1(
        .clk_i(clk),
        .rst_n_i(rst_n),
        .start_i(start),
        .done_o(done_fc1),

        .input_data_addr_o(fc1_input_data_addr),
        .input_data_i(fc1_input_data),

        .weight_addr_o(fc1_weight_addr),
        .weight_i(fc1_weight),

        .bias_addr_o(fc1_bias_addr),
        .bias_i(fc1_bias),

        .fc_output_wren_o(fc1_output_wren),
        .fc_output_data_o(fc1_output_data),

        .mul_data1_o(fc1_mul_data1),
        .mul_data2_o(fc1_mul_data2),
        .mul_result_i(fc1_mul_result)
    );

    assign fc1_output_data_monitor = fc1_output_data;
    assign fc1_output_wren_monitor = fc1_output_wren && !write_done_1;

    // 每32个输出通道写入一个RAM地址
    reg [0:1023] write_buffer_1[0:3];
    reg [4:0] write_count_1;
    reg [1:0] write_addr_1;
    reg [1:0] write_addr_1_dly;
    reg write_en_1;

    reg write_done_1;

    // 初始化 write_buffer_1，避免未定义值
    // !这里可能不能综合
    integer i;
    initial begin
        for (i = 0; i < 4; i = i + 1) begin
            write_buffer_1[i] = 0;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            write_count_1 <= 0;
            write_addr_1 <= 0;
            write_done_1 <= 0;
        end
        else if (fc1_output_wren_monitor) begin
            write_buffer_1[write_addr_1][write_count_1*32 +: 32] <= fc1_output_data_monitor;
            if (write_count_1 == 31) begin
                write_count_1 <= 0;
                if (write_addr_1 == 3) begin
                    write_done_1 <= 1;
                end else begin
                    write_addr_1 <= write_addr_1 + 1;
                end
            end
            else begin
                write_count_1 <= write_count_1 + 1;
            end
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            write_addr_1_dly <= 0;
        end
        else begin
            write_addr_1_dly <= write_addr_1;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            write_en_1 <= 0;
        end
        else begin
            if (!write_done_1 && fc1_output_wren_monitor) begin
                write_en_1 <= 1;
            end else begin
                write_en_1 <= 0;
            end
        end
    end

    buffer1 u_buffer1(
        .clka(clk),
        .ena(write_en_1),
        .wea(1'b1),
        .addra(write_addr_1_dly),
        .dina(write_buffer_1[write_addr_1_dly]),

        .clkb(clk),
        .enb(write_done_1),
        .addrb(fc2_input_data_addr),
        .doutb(fc2_input_data)
    );

    // Instantiate fc_2
    fc2_weight_ram u_fc2_weight(
        .addra(fc2_weight_addr),
        .clka(clk),
        .douta(fc2_weight),
        .wea(1'b0)
    );

    fc2_bias_ram u_fc2_bias(
        .addra(fc2_bias_addr),
        .clka(clk),
        .douta(fc2_bias),
        .wea(1'b0)
    );

    mult_array2 u_fc2_mult_array(
        .clk(clk),
        .rst_n(rst_n),
        .data1(fc2_mul_data1),
        .data2(fc2_mul_data2),
        .result(fc2_mul_result)
    );

    fc_2 u_fc2(
        .clk_i(clk),
        .rst_n_i(rst_n),
        // done_fc1比write_done_1提前一个周期,这里暂时使用write_done_1
        .start_i(write_done_1),
        // .start_i(done_fc1),
        .done_o(done_fc2),

        .input_data_addr_o(fc2_input_data_addr),
        .input_data_i(fc2_input_data),

        .weight_addr_o(fc2_weight_addr),
        .weight_i(fc2_weight),

        .bias_addr_o(fc2_bias_addr),
        .bias_i(fc2_bias),

        .fc_output_wren_o(fc2_output_wren),
        .fc_output_data_o(fc2_output_data),

        .mul_data1_o(fc2_mul_data1),
        .mul_data2_o(fc2_mul_data2),
        .mul_result_i(fc2_mul_result)
    );

    reg write_done_2;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            write_done_2 <= 0;
        end
        else begin
            if (done_fc2) begin
                write_done_2 <= done_fc2;
            end
        end
    end

    assign fc2_output_data_monitor = fc2_output_data;
    assign fc2_output_wren_monitor = fc2_output_wren && !write_done_2;
    
    // 定义 fc2_output_concat 和计数器
    reg [1535:0] fc2_output_concat;
    reg [4:0]    fc2_output_count;
    reg          fc2_output_concat_done;
    reg [10:0]   fc2_output_index;  // 用于计算索引位置

    // 初始化 fc2_output_concat 和计数器，避免未定义值
    initial begin
        fc2_output_concat = 1536'd0;
        fc2_output_count = 5'd0;
        fc2_output_concat_done = 0;
        fc2_output_index = 11'd1488;
    end

    reg fc2_output_wren_monitor_dly;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            fc2_output_wren_monitor_dly <= 0;
        end else begin
            fc2_output_wren_monitor_dly <= fc2_output_wren_monitor;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            fc2_output_concat <= 1536'd0;
            fc2_output_count <= 5'd0;
            fc2_output_concat_done <= 0;
            fc2_output_index <= 11'd1488;
        end
        else if (fc2_output_wren_monitor) begin
            fc2_output_index <= 1440 - 48 * fc2_output_count;
            fc2_output_concat[fc2_output_index +: 48] <= fc2_output_data_monitor;
            if (fc2_output_count == 31) begin
                fc2_output_concat_done <= 1;
            end else begin
                fc2_output_count <= fc2_output_count + 1;
            end
        end
    end

    fc3_weight_ram u_fc3_weight(
        .addra(fc3_weight_addr),
        .clka(clk),
        .douta(fc3_weight),
        .wea(1'b0)
    );

    fc3_bias_ram u_fc3_bias(
        .addra(fc3_bias_addr),
        .clka(clk),
        .douta(fc3_bias),
        .wea(1'b0)
    );

    mult_array3 u_fc3_mult_array(
        .clk(clk),
        .rst_n(rst_n),
        .data1(fc3_mul_data1),
        .data2(fc3_mul_data2),
        .result(fc3_mul_result)
    );


    // Instantiate fc_3
    fc_3 u_fc3(
        .clk_i(clk),
        .rst_n_i(rst_n),
        .start_i(fc2_output_concat_done),
        .done_o(done_fc3),

        .input_data_i(fc2_output_concat),

        .weight_addr_o(fc3_weight_addr),
        .weight_i(fc3_weight),

        .bias_addr_o(fc3_bias_addr),
        .bias_i(fc3_bias),

        .fc_output_wren_o(fc3_output_wren),
        .fc_output_data_o(fc3_output_data),

        .mul_data1_o(fc3_mul_data1),
        .mul_data2_o(fc3_mul_data2),
        .mul_result_i(fc3_mul_result)
    );

    reg write_done3;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            write_done3 <= 0;
        end
        else begin
            if (done_fc3) begin
                write_done3 <= done_fc3;
            end
        end
    end

    assign fc3_output_data_monitor = fc3_output_data;
    assign fc3_output_wren_monitor = fc3_output_wren && !write_done3;
    assign done_o = done_fc3;

endmodule
